<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=2"><meta name="theme-color" content="#222"><meta name="generator" content="Hexo 5.4.0"><link rel="apple-touch-icon" sizes="180x180" href="/hexoblog/images/apple-touch-icon-next.png"><link rel="icon" type="image/png" sizes="32x32" href="/hexoblog/images/favicon-32x32-next.png"><link rel="icon" type="image/png" sizes="16x16" href="/hexoblog/images/favicon-16x16-next.png"><link rel="mask-icon" href="/hexoblog/images/logo.svg" color="#222"><link rel="stylesheet" href="/hexoblog/css/main.css"><link rel="stylesheet" href="https://unpkg.com/@fortawesome/fontawesome-free@5.15.3/css/all.min.css"><link rel="stylesheet" href="https://unpkg.com/animate.css@3.1.1/animate.min.css"><link rel="stylesheet" href="https://unpkg.com/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css"><script class="hexo-configurations">var NexT=window.NexT||{},CONFIG={hostname:"linqiankun.github.io",root:"/hexoblog/",images:"/hexoblog/images",scheme:"Gemini",version:"8.3.0",exturl:!1,sidebar:{position:"left",display:"post",padding:18,offset:12},copycode:!0,bookmark:{enable:!0,color:"#222",save:"auto"},fancybox:!0,mediumzoom:!1,lazyload:!1,pangu:!0,comments:{style:"tabs",active:null,storage:!0,lazyload:!1,nav:null},motion:{enable:!0,async:!1,transition:{post_block:"fadeIn",post_header:"fadeInDown",post_body:"fadeInDown",coll_header:"fadeInLeft",sidebar:"fadeInUp"}},prism:!1,i18n:{placeholder:"搜索...",empty:"没有找到任何搜索结果：${query}",hits_time:"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）",hits:"找到 ${hits} 个搜索结果"},path:"/hexoblog/search.xml",localsearch:{enable:!0,trigger:"auto",top_n_per_article:1,unescape:!1,preload:!1}}</script><meta name="description" content="介绍服务调用过程推荐"><meta property="og:type" content="article"><meta property="og:title" content="dubbo服务调用过程"><meta property="og:url" content="https://linqiankun.github.io/hexoblog/md/rpc/dubbo/dubbo%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E8%BF%87%E7%A8%8B/index.html"><meta property="og:site_name" content="Study"><meta property="og:description" content="介绍服务调用过程推荐"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://hexoimage.oss-cn-hangzhou.aliyuncs.com/hexobolg/java/export-refer.jpg"><meta property="article:published_time" content="2021-04-15T11:57:16.000Z"><meta property="article:modified_time" content="2024-05-14T07:26:14.765Z"><meta property="article:author" content="九分石人"><meta property="article:tag" content="dubbo"><meta property="article:tag" content="rpc"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://hexoimage.oss-cn-hangzhou.aliyuncs.com/hexobolg/java/export-refer.jpg"><link rel="canonical" href="https://linqiankun.github.io/hexoblog/md/rpc/dubbo/dubbo%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E8%BF%87%E7%A8%8B/"><script class="page-configurations">CONFIG.page={sidebar:"",isHome:!1,isPost:!0,lang:"zh-CN"}</script><title>dubbo服务调用过程 | Study</title><noscript><style>body{margin-top:2rem}.use-motion .collection-header,.use-motion .comments,.use-motion .menu-item,.use-motion .pagination,.use-motion .post-block,.use-motion .post-body,.use-motion .post-header,.use-motion .sidebar{visibility:visible}.use-motion .footer,.use-motion .header,.use-motion .site-brand-container .toggle{opacity:initial}.use-motion .custom-logo-image,.use-motion .site-subtitle,.use-motion .site-title{opacity:initial;top:initial}.use-motion .logo-line{transform:scaleX(1)}.search-pop-overlay,.sidebar-nav{display:none}.sidebar-panel{display:block}</style></noscript><link rel="alternate" href="/hexoblog/atom.xml" title="Study" type="application/atom+xml"><style>.darkmode--activated{--body-bg-color:#282828;--content-bg-color:#333;--card-bg-color:#555;--text-color:#ccc;--blockquote-color:#bbb;--link-color:#ccc;--link-hover-color:#eee;--brand-color:#ddd;--brand-hover-color:#ddd;--table-row-odd-bg-color:#282828;--table-row-hover-bg-color:#363636;--menu-item-bg-color:#555;--btn-default-bg:#222;--btn-default-color:#ccc;--btn-default-border-color:#555;--btn-default-hover-bg:#666;--btn-default-hover-color:#ccc;--btn-default-hover-border-color:#666;--highlight-background:#282b2e;--highlight-foreground:#a9b7c6;--highlight-gutter-background:#34393d;--highlight-gutter-foreground:#9ca9b6}.darkmode--activated img{opacity:.75}.darkmode--activated img:hover{opacity:.9}.darkmode--activated code{color:#69dbdc;background:0 0}button.darkmode-toggle{z-index:9999}.darkmode-ignore,img{display:flex!important}.beian img{display:inline-block!important}</style></head><body itemscope itemtype="http://schema.org/WebPage" class="use-motion"><div class="headband"></div><main class="main"><header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="header-inner"><div class="site-brand-container"><div class="site-nav-toggle"><div class="toggle" aria-label="切换导航栏" role="button"><span class="toggle-line"></span> <span class="toggle-line"></span> <span class="toggle-line"></span></div></div><div class="site-meta"><a href="/hexoblog/" class="brand" rel="start"><i class="logo-line"></i><h1 class="site-title">Study</h1><i class="logo-line"></i></a><p class="site-subtitle" itemprop="description">好好学习，天天向上！</p></div><div class="site-nav-right"><div class="toggle popup-trigger"><i class="fa fa-search fa-fw fa-lg"></i></div></div></div><nav class="site-nav"><ul class="main-menu menu"><li class="menu-item menu-item-home"><a href="/hexoblog/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li><li class="menu-item menu-item-about"><a href="/hexoblog/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a></li><li class="menu-item menu-item-tags"><a href="/hexoblog/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li><li class="menu-item menu-item-categories"><a href="/hexoblog/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li><li class="menu-item menu-item-archives"><a href="/hexoblog/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li><li class="menu-item menu-item-sitemap"><a href="/hexoblog/sitemap.xml" rel="section"><i class="fa fa-sitemap fa-fw"></i>站点地图</a></li><li class="menu-item menu-item-search"><a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索</a></li></ul></nav><div class="search-pop-overlay"><div class="popup search-popup"><div class="search-header"><span class="search-icon"><i class="fa fa-search"></i></span><div class="search-input-container"><input autocomplete="off" autocapitalize="off" maxlength="80" placeholder="搜索..." spellcheck="false" type="search" class="search-input"></div><span class="popup-btn-close" role="button"><i class="fa fa-times-circle"></i></span></div><div class="search-result-container no-result"><div class="search-result-icon"><i class="fa fa-spinner fa-pulse fa-5x"></i></div></div></div></div></div><div class="toggle sidebar-toggle" role="button"><span class="toggle-line"></span> <span class="toggle-line"></span> <span class="toggle-line"></span></div><aside class="sidebar"><div class="sidebar-inner sidebar-nav-active sidebar-toc-active"><ul class="sidebar-nav"><li class="sidebar-nav-toc">文章目录</li><li class="sidebar-nav-overview">站点概览</li></ul><div class="sidebar-panel-container"><div class="post-toc-wrap sidebar-panel"><div class="post-toc animated"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%BB%8B%E7%BB%8D"><span class="nav-number">1.</span> <span class="nav-text">介绍</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B0%83%E7%94%A8%E7%9A%84%E6%97%B6%E5%BA%8F%E5%9B%BE"><span class="nav-number">1.1.</span> <span class="nav-text">调用的时序图</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%9A%B4%E9%9C%B2%E8%BF%87%E7%A8%8B"><span class="nav-number">1.2.</span> <span class="nav-text">暴露过程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%BC%95%E5%85%A5%E8%BF%87%E7%A8%8B"><span class="nav-number">1.3.</span> <span class="nav-text">引入过程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B"><span class="nav-number">1.4.</span> <span class="nav-text">调用流程</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E8%B0%83%E7%94%A8%E8%BF%87%E7%A8%8B"><span class="nav-number">2.</span> <span class="nav-text">调用过程</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B6%88%E8%B4%B9%E7%AB%AF%E5%8F%91%E8%B5%B7%E8%AF%B7%E6%B1%82"><span class="nav-number">2.1.</span> <span class="nav-text">消费端发起请求</span></a></li></ol></li></ol></div></div><div class="site-overview-wrap sidebar-panel"><div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person"><img class="site-author-image" itemprop="image" alt="九分石人" src="/hexoblog/images/avatar.png"><p class="site-author-name" itemprop="name">九分石人</p><div class="site-description" itemprop="description">my blogs</div></div><div class="site-state-wrap site-overview-item animated"><nav class="site-state"><div class="site-state-item site-state-posts"><a href="/hexoblog/archives/"><span class="site-state-item-count">239</span> <span class="site-state-item-name">日志</span></a></div><div class="site-state-item site-state-categories"><a href="/hexoblog/categories/"><span class="site-state-item-count">35</span> <span class="site-state-item-name">分类</span></a></div><div class="site-state-item site-state-tags"><a href="/hexoblog/tags/"><span class="site-state-item-count">64</span> <span class="site-state-item-name">标签</span></a></div></nav></div><div class="links-of-author site-overview-item animated"><span class="links-of-author-item"><a href="https://github.com/linqiankun" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;linqiankun" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a> </span><span class="links-of-author-item"><a href="linqiankun:191580378@qq.com" title="E-Mail → linqiankun:191580378@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a> </span><span class="links-of-author-item"><a href="https://weibo.com/jiufenshiren" title="Weibo → https:&#x2F;&#x2F;weibo.com&#x2F;jiufenshiren" rel="noopener" target="_blank"><i class="fab fa-weibo fa-fw"></i>Weibo</a> </span><span class="links-of-author-item"><a href="/hexoblog/atom.xml" title="Rss → &#x2F;atom.xml"><i class="fa fa-rss fa-fw"></i>Rss</a></span></div><div class="cc-license site-overview-item animated" itemprop="license"><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/zh-CN" class="cc-opacity" rel="noopener" target="_blank"><img src="/hexoblog/images/cc-by-nc-sa.svg" alt="Creative Commons"></a></div><div class="links-of-blogroll site-overview-item animated"><div class="links-of-blogroll-title"><i class="fa fa-globe fa-fw"></i> Links</div><ul class="links-of-blogroll-list"><li class="links-of-blogroll-item"><a href="http://124.222.164.140:3001/workspace/hexoblog" title="http:&#x2F;&#x2F;124.222.164.140:3001&#x2F;workspace&#x2F;hexoblog" rel="noopener" target="_blank">AnythingLLM</a></li><li class="links-of-blogroll-item"><a href="https://www.yuque.com/jiufenshiren" title="https:&#x2F;&#x2F;www.yuque.com&#x2F;jiufenshiren" rel="noopener" target="_blank">YuQue</a></li><li class="links-of-blogroll-item"><a href="https://github.com/LinQiankun" title="https:&#x2F;&#x2F;github.com&#x2F;LinQiankun" rel="noopener" target="_blank">Github</a></li><li class="links-of-blogroll-item"><a href="https://gitee.com/linqiankun" title="https:&#x2F;&#x2F;gitee.com&#x2F;linqiankun" rel="noopener" target="_blank">Gitee</a></li><li class="links-of-blogroll-item"><a href="https://my.oschina.net/linqiankun" title="https:&#x2F;&#x2F;my.oschina.net&#x2F;linqiankun" rel="noopener" target="_blank">OsChina</a></li><li class="links-of-blogroll-item"><a href="https://segmentfault.com/u/jiufenshiren" title="https:&#x2F;&#x2F;segmentfault.com&#x2F;u&#x2F;jiufenshiren" rel="noopener" target="_blank">Segmentfault</a></li><li class="links-of-blogroll-item"><a href="https://juejin.cn/user/3263006243306205" title="https:&#x2F;&#x2F;juejin.cn&#x2F;user&#x2F;3263006243306205" rel="noopener" target="_blank">Juejin</a></li><li class="links-of-blogroll-item"><a href="https://blog.csdn.net/qq_36985536?spm=1010.2135.3001.5421" title="https:&#x2F;&#x2F;blog.csdn.net&#x2F;qq_36985536?spm&#x3D;1010.2135.3001.5421" rel="noopener" target="_blank">CSDN</a></li><li class="links-of-blogroll-item"><a href="https://www.cnblogs.com/jiufenshiren" title="https:&#x2F;&#x2F;www.cnblogs.com&#x2F;jiufenshiren" rel="noopener" target="_blank">CnBlogs</a></li><li class="links-of-blogroll-item"><a href="https://home.51cto.com/space?uid=12848310" title="https:&#x2F;&#x2F;home.51cto.com&#x2F;space?uid&#x3D;12848310" rel="noopener" target="_blank">51CTO</a></li></ul></div><script data-embed-id="94f790d6-f7ac-4d04-ad53-55d841525b5a" data-base-api-url="http://124.222.164.140:3001/api/embed" src="http://124.222.164.140:3001/embed/anythingllm-chat-widget.min.js"></script></div></div></div></aside><div class="sidebar-dimmer"></div></header><div class="back-to-top" role="button" aria-label="返回顶部"><i class="fa fa-arrow-up"></i> <span>0%</span></div><a role="button" class="book-mark-link book-mark-link-fixed"></a><noscript><div class="noscript-warning">Theme NexT works best with JavaScript enabled</div></noscript><div class="main-inner post posts-expand"><div class="post-block"><article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN"><link itemprop="mainEntityOfPage" href="https://linqiankun.github.io/hexoblog/md/rpc/dubbo/dubbo%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E8%BF%87%E7%A8%8B/"><span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="image" content="/hexoblog/images/avatar.png"><meta itemprop="name" content="九分石人"><meta itemprop="description" content="my blogs"></span><span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="Study"></span><header class="post-header"><h1 class="post-title" itemprop="name headline">dubbo服务调用过程</h1><div class="post-meta-container"><div class="post-meta"><span class="post-meta-item"><span class="post-meta-item-icon"><i class="far fa-calendar"></i> </span><span class="post-meta-item-text">发表于</span> <time title="创建时间：2021-04-15 19:57:16 19:57:16" itemprop="dateCreated datePublished" datetime="2021-04-15T19:57:16+08:00">2021-04-15 19:57:16</time> </span><span class="post-meta-item"><span class="post-meta-item-icon"><i class="far fa-calendar-check"></i> </span><span class="post-meta-item-text">更新于</span> <time title="修改时间：2024-05-14 15:26:14 15:26:14" itemprop="dateModified" datetime="2024-05-14T15:26:14+08:00">2024-05-14 15:26:14</time> </span><span class="post-meta-item"><span class="post-meta-item-icon"><i class="far fa-folder"></i> </span><span class="post-meta-item-text">分类于</span> <span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/hexoblog/categories/rpc/" itemprop="url" rel="index"><span itemprop="name">rpc</span></a> </span>， <span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/hexoblog/categories/rpc/dubbo/" itemprop="url" rel="index"><span itemprop="name">dubbo</span></a> </span></span><span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display:none"><span class="post-meta-item-icon"><i class="far fa-eye"></i> </span><span class="post-meta-item-text">阅读次数：</span> <span id="busuanzi_value_page_pv"></span></span></div><div class="post-meta"><span class="post-meta-item" title="本文字数"><span class="post-meta-item-icon"><i class="far fa-file-word"></i> </span><span class="post-meta-item-text">本文字数：</span> <span>16k</span> </span><span class="post-meta-item" title="阅读时长"><span class="post-meta-item-icon"><i class="far fa-clock"></i> </span><span class="post-meta-item-text">阅读时长 &asymp;</span> <span>14 分钟</span></span></div></div></header><div class="post-body" itemprop="articleBody"><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p><a target="_blank" rel="noopener" href="https://dubbo.apache.org/zh/docs/v2.7/dev/implementation/#%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%E7%BB%86%E8%8A%82">服务调用过程</a><br><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/01bf8ded203c">推荐</a></p><span id="more"></span><h2 id="调用的时序图"><a href="#调用的时序图" class="headerlink" title="调用的时序图"></a>调用的时序图</h2><p><img src="https://hexoimage.oss-cn-hangzhou.aliyuncs.com/hexobolg/java/export-refer.jpg" alt="调用时序图"></p><h2 id="暴露过程"><a href="#暴露过程" class="headerlink" title="暴露过程"></a>暴露过程</h2><p>&emsp;&emsp;首先provider启动，通过Proxy组件根据具体的协议，将需要暴露的接口封装成invoker，invoker是dubbo一个很核心的组件，代表一个可执行体。<br>&emsp;&emsp;然后再通过Exporter包装一下，这是为了在注册中心暴露自己套的一层。然后将Exporter通过Registry注册到注册中心。 这就是整体服务暴露过程。<br>&emsp;&emsp;暴露会生成Exporter和服务提供invoker，会将Exporter保存在ExporterMap中，在调用请求到来时，会在这个map中找到对应的Exporter然后逐步还原出原来的invoker。</p><h2 id="引入过程"><a href="#引入过程" class="headerlink" title="引入过程"></a>引入过程</h2><p>&emsp;&emsp;首先消费者启动会向注册中心拉取服务提供者的元信息，然后调用流程也是从 Proxy 开始，毕竟都需要代理才能无感知。<br>&emsp;&emsp;Proxy持有一个invoker对象，调用invoke之后需要通过Cluster先从Directory获取所有可调用的远程服务的Invoker列表，如果配置了某些路由规则，比如某个接口只能调用某个节点的那就再过滤一遍Invoker列表。<br>&emsp;&emsp;剩下的Invoker再通过LoadBalance做负载均衡选取一个。然后再经过Filter做一些统计什么的，再通过Client做数据传输，比如用Netty来传输。<br>&emsp;&emsp;传输需要通过Codec做协议构造，再序化，最终发往对应的服务提供者。<br>&emsp;&emsp;服务提供者接收到之后也会进行Codec协议处理，然后反序列化后将请求扔到线程池处理。某个线程会根据请求找到对应的Exporter，找到Exporter就是找到了invoker，经过一层层过滤链之后最终实现调用，最后原路返回。</p><h2 id="调用流程"><a href="#调用流程" class="headerlink" title="调用流程"></a>调用流程</h2><p><a target="_blank" rel="noopener" href="https://dubbo.apache.org/zh/docs/v2.7/dev/implementation/#%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%E7%BB%86%E8%8A%82">远程调用细节</a></p><p>&emsp;&emsp;服务暴露时，provider启动，Proxy组件会将ref对象（需要暴露的接口）封装成invoker对象（服务提供invoker，AbstractProxyInvoker），就是实际提供服务的对象的代理。<br>&emsp;&emsp;通过Protocol将invoker对象包装成为Exporter对象保存在ExporterMap中，再在注册中心创建节点，订阅通知，将自己注册到注册中心。<br>&emsp;&emsp;在调用前，服务引入时，会在注册中心注册消费者节点，订阅通知，获取所有的服务提供者消息（服务提供方export对象信息，内含invoker对象信息）。<br>&emsp;&emsp;会使用到消费方的Proxy对象（这个对象在消费者初始化完成的时候生成，会注入到容器中），它持有一个消费invoker对象（dubboinvoker等），通过Cluster从Directory获取所有可调用的远程服务Invoker列表。<br>&emsp;&emsp;在消费方获取到提供方invoker列表信息后，会根据负载均衡等策略确定需要调用的具体提供invoker对象。<br>&emsp;&emsp;我们在项目发起调用后，消费代理对象，即Proxy组件会执行持有invoker对象（这里是消费invoker）的invoke方法，doinvoke方法。<br>&emsp;&emsp;doinvoke方法底层，会经过协议构造，序列化后，经过netty客户端，发往服务提供方。<br>&emsp;&emsp;提供方收到netty请求信息后，也会进行协议构造，反序列化后，将调用请求放入dubbo线程池（貌似默认是200）。<br>&emsp;&emsp;线程池的请求开始执行，会根据请求的信息，在ExporterMap中找到对应的exporter对象，也就是拿到了对应的服务提供invoker对象，就是实际服务提供的代理，然后执行我们提供的方法，完成调用。</p><hr><h1 id="调用过程"><a href="#调用过程" class="headerlink" title="调用过程"></a>调用过程</h1><p>&emsp;&emsp;服务暴露过程，与服务引入过程在单独的笔记，这里只有实际调用过程。</p><h2 id="消费端发起请求"><a href="#消费端发起请求" class="headerlink" title="消费端发起请求"></a>消费端发起请求</h2><p>&emsp;&emsp;在消费者初始化的时候，会生成一个消费者代理注册到容器中，消费调用接口时会把服务接口的method对象和参数放到RpcInvocation对象中，传入MockClusterInvoker.invoke，这个主要是看是否配置mock，一般情况下不走mock，会调用FailOverClusterInvoker.invoke。在服务接口消费者初始化时，接口方法和提供者Invoker对应关系保存在RegistryDirectory的methodInvokerMap中，通过调用的方法名称（或方法名称+第一个参数）获得对应的提供者invoker列表，如注册中心设置了路由规则，对这些invoker根据路由规则进行过滤。</p><p>&emsp;&emsp;这里先从Directory中拿到了invoker列表。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AbstractDirectory</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Invoker&lt;T&gt;&gt; list(Invocation invocation) <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    <span class="keyword">if</span> (destroyed) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RpcException(<span class="string">&quot;Directory already destroyed .url: &quot;</span> + getUrl());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> doList(invocation);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// RegistryDirectory</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Invoker&lt;T&gt;&gt; doList(Invocation invocation) &#123;</span><br><span class="line">    <span class="keyword">if</span> (forbidden) &#123;</span><br><span class="line">        <span class="comment">// 1. No service provider 2. Service providers are disabled</span></span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RpcException(RpcException.FORBIDDEN_EXCEPTION, <span class="string">&quot;No provider available from registry &quot;</span> +</span><br><span class="line">                getUrl().getAddress() + <span class="string">&quot; for service &quot;</span> + getConsumerUrl().getServiceKey() + <span class="string">&quot; on consumer &quot;</span> +</span><br><span class="line">                NetUtils.getLocalHost() + <span class="string">&quot; use dubbo version &quot;</span> + Version.getVersion() +</span><br><span class="line">                <span class="string">&quot;, please check status of providers(disabled, not registered or in blacklist).&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (multiGroup) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.invokers == <span class="keyword">null</span> ? Collections.emptyList() : <span class="keyword">this</span>.invokers;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    List&lt;Invoker&lt;T&gt;&gt; invokers = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// Get invokers from cache, only runtime routers will be executed.</span></span><br><span class="line">        invokers = routerChain.route(getConsumerUrl(), invocation);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;Failed to execute router: &quot;</span> + getUrl() + <span class="string">&quot;, cause: &quot;</span> + t.getMessage(), t);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// FIXME Is there any need of failing back to Constants.ANY_VALUE or the first available method invokers when invokers is null?</span></span><br><span class="line">    <span class="comment">/*Map&lt;String, List&lt;Invoker&lt;T&gt;&gt;&gt; localMethodInvokerMap = this.methodInvokerMap; // local reference</span></span><br><span class="line"><span class="comment">    if (localMethodInvokerMap != null &amp;&amp; localMethodInvokerMap.size() &gt; 0) &#123;</span></span><br><span class="line"><span class="comment">        String methodName = RpcUtils.getMethodName(invocation);</span></span><br><span class="line"><span class="comment">        invokers = localMethodInvokerMap.get(methodName);</span></span><br><span class="line"><span class="comment">        if (invokers == null) &#123;</span></span><br><span class="line"><span class="comment">            invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);</span></span><br><span class="line"><span class="comment">        &#125;</span></span><br><span class="line"><span class="comment">        if (invokers == null) &#123;</span></span><br><span class="line"><span class="comment">            Iterator&lt;List&lt;Invoker&lt;T&gt;&gt;&gt; iterator = localMethodInvokerMap.values().iterator();</span></span><br><span class="line"><span class="comment">            if (iterator.hasNext()) &#123;</span></span><br><span class="line"><span class="comment">                invokers = iterator.next();</span></span><br><span class="line"><span class="comment">            &#125;</span></span><br><span class="line"><span class="comment">        &#125;</span></span><br><span class="line"><span class="comment">    &#125;*/</span></span><br><span class="line">    <span class="keyword">return</span> invokers == <span class="keyword">null</span> ? Collections.emptyList() : invokers;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;在读取到所有的invoke列表后，会根据负载均衡算法选择一个进行调用。<br>&emsp;&emsp;这里也是用了SPI机制，支持多种负载均衡算法：随机，RR循环，最不活跃，一致性hash，默认随机。</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">random=org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance</span><br><span class="line">roundrobin=org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance</span><br><span class="line">leastactive=org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance</span><br><span class="line">consistenthash=org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;确定调用对象后，就会调用对应的doinvoke方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@SuppressWarnings(&#123;&quot;unchecked&quot;, &quot;rawtypes&quot;&#125;)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Result <span class="title">doInvoke</span><span class="params">(Invocation invocation, <span class="keyword">final</span> List&lt;Invoker&lt;T&gt;&gt; invokers, LoadBalance loadbalance)</span> <span class="keyword">throws</span> RpcException </span>&#123;</span><br><span class="line">    List&lt;Invoker&lt;T&gt;&gt; copyInvokers = invokers;</span><br><span class="line">    checkInvokers(copyInvokers, invocation);</span><br><span class="line">    String methodName = RpcUtils.getMethodName(invocation);</span><br><span class="line">    <span class="keyword">int</span> len = getUrl().getMethodParameter(methodName, Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">if</span> (len &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">        len = <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// retry loop.</span></span><br><span class="line">    RpcException le = <span class="keyword">null</span>; <span class="comment">// last exception.</span></span><br><span class="line">    List&lt;Invoker&lt;T&gt;&gt; invoked = <span class="keyword">new</span> ArrayList&lt;Invoker&lt;T&gt;&gt;(copyInvokers.size()); <span class="comment">// invoked invokers.</span></span><br><span class="line">    Set&lt;String&gt; providers = <span class="keyword">new</span> HashSet&lt;String&gt;(len);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; len; i++) &#123;</span><br><span class="line">        <span class="comment">//Reselect before retry to avoid a change of candidate `invokers`.</span></span><br><span class="line">        <span class="comment">//<span class="doctag">NOTE:</span> if `invokers` changed, then `invoked` also lose accuracy.</span></span><br><span class="line">        <span class="keyword">if</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            checkWhetherDestroyed();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 获取所有的服务提供invoker列表</span></span><br><span class="line">            copyInvokers = list(invocation);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// check again</span></span><br><span class="line">            checkInvokers(copyInvokers, invocation);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 通过负载均衡选择实际调用的目标</span></span><br><span class="line">        Invoker&lt;T&gt; invoker = select(loadbalance, invocation, copyInvokers, invoked);</span><br><span class="line"></span><br><span class="line">        invoked.add(invoker);</span><br><span class="line">        RpcContext.getContext().setInvokers((List) invoked);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Result result = invoker.invoke(invocation);</span><br><span class="line">            <span class="keyword">if</span> (le != <span class="keyword">null</span> &amp;&amp; logger.isWarnEnabled()) &#123;</span><br><span class="line">                logger.warn(<span class="string">&quot;Although retry the method &quot;</span> + methodName</span><br><span class="line">                        + <span class="string">&quot; in the service &quot;</span> + getInterface().getName()</span><br><span class="line">                        + <span class="string">&quot; was successful by the provider &quot;</span> + invoker.getUrl().getAddress()</span><br><span class="line">                        + <span class="string">&quot;, but there have been failed providers &quot;</span> + providers</span><br><span class="line">                        + <span class="string">&quot; (&quot;</span> + providers.size() + <span class="string">&quot;/&quot;</span> + copyInvokers.size()</span><br><span class="line">                        + <span class="string">&quot;) from the registry &quot;</span> + directory.getUrl().getAddress()</span><br><span class="line">                        + <span class="string">&quot; on the consumer &quot;</span> + NetUtils.getLocalHost()</span><br><span class="line">                        + <span class="string">&quot; using the dubbo version &quot;</span> + Version.getVersion() + <span class="string">&quot;. Last error is: &quot;</span></span><br><span class="line">                        + le.getMessage(), le);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (RpcException e) &#123;</span><br><span class="line">            <span class="keyword">if</span> (e.isBiz()) &#123; <span class="comment">// biz exception.</span></span><br><span class="line">                <span class="keyword">throw</span> e;</span><br><span class="line">            &#125;</span><br><span class="line">            le = e;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            le = <span class="keyword">new</span> RpcException(e.getMessage(), e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            providers.add(invoker.getUrl().getAddress());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> RpcException(le.getCode(), <span class="string">&quot;Failed to invoke the method &quot;</span></span><br><span class="line">            + methodName + <span class="string">&quot; in the service &quot;</span> + getInterface().getName()</span><br><span class="line">            + <span class="string">&quot;. Tried &quot;</span> + len + <span class="string">&quot; times of the providers &quot;</span> + providers</span><br><span class="line">            + <span class="string">&quot; (&quot;</span> + providers.size() + <span class="string">&quot;/&quot;</span> + copyInvokers.size()</span><br><span class="line">            + <span class="string">&quot;) from the registry &quot;</span> + directory.getUrl().getAddress()</span><br><span class="line">            + <span class="string">&quot; on the consumer &quot;</span> + NetUtils.getLocalHost() + <span class="string">&quot; using the dubbo version &quot;</span></span><br><span class="line">            + Version.getVersion() + <span class="string">&quot;. Last error is: &quot;</span></span><br><span class="line">            + le.getMessage(), le.getCause() != <span class="keyword">null</span> ? le.getCause() : le);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;methodInvokerMap保存的是持有DubboInvoker（dubbo协议）实例的InvokerDelegete对象，是Invoker-Filter链的头部，先激活Filter连然后最终调到DubboInvoker.invoke(RpcInvocation)。<br>&emsp;&emsp;从FailoverClusterInvoker的doinvoke方法会进入AbstractInvoker的invoke方法，最终根据协议头进入具体的invoker中，执行doinvoke方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FailoverClusterInvoker</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> Result <span class="title">doInvoke</span><span class="params">(<span class="keyword">final</span> Invocation invocation)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">    RpcInvocation inv = (RpcInvocation) invocation;</span><br><span class="line">    <span class="keyword">final</span> String methodName = RpcUtils.getMethodName(invocation);</span><br><span class="line">    inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());</span><br><span class="line">    inv.setAttachment(Constants.VERSION_KEY, version);</span><br><span class="line"></span><br><span class="line">    ExchangeClient currentClient;</span><br><span class="line">    <span class="keyword">if</span> (clients.length == <span class="number">1</span>) &#123;</span><br><span class="line">        currentClient = clients[<span class="number">0</span>];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        currentClient = clients[index.getAndIncrement() % clients.length];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">boolean</span> isAsync = RpcUtils.isAsync(getUrl(), invocation);</span><br><span class="line">        <span class="keyword">boolean</span> isAsyncFuture = RpcUtils.isReturnTypeFuture(inv);</span><br><span class="line">        <span class="keyword">boolean</span> isOneway = RpcUtils.isOneway(getUrl(), invocation);</span><br><span class="line">        <span class="keyword">int</span> timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);</span><br><span class="line">        <span class="keyword">if</span> (isOneway) &#123;</span><br><span class="line">            <span class="keyword">boolean</span> isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, <span class="keyword">false</span>);</span><br><span class="line">            currentClient.send(inv, isSent);</span><br><span class="line">            RpcContext.getContext().setFuture(<span class="keyword">null</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> RpcResult();</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isAsync) &#123;</span><br><span class="line">            ResponseFuture future = currentClient.request(inv, timeout);</span><br><span class="line">            <span class="comment">// For compatibility</span></span><br><span class="line">            FutureAdapter&lt;Object&gt; futureAdapter = <span class="keyword">new</span> FutureAdapter&lt;&gt;(future);</span><br><span class="line">            RpcContext.getContext().setFuture(futureAdapter);</span><br><span class="line"></span><br><span class="line">            Result result;</span><br><span class="line">            <span class="keyword">if</span> (isAsyncFuture) &#123;</span><br><span class="line">                <span class="comment">// register resultCallback, sometimes we need the async result being processed by the filter chain.</span></span><br><span class="line">                result = <span class="keyword">new</span> AsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), <span class="keyword">false</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                result = <span class="keyword">new</span> SimpleAsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), <span class="keyword">false</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            RpcContext.getContext().setFuture(<span class="keyword">null</span>);</span><br><span class="line">            <span class="keyword">return</span> (Result) currentClient.request(inv, timeout).get();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (TimeoutException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RpcException(RpcException.TIMEOUT_EXCEPTION, <span class="string">&quot;Invoke remote method timeout. method: &quot;</span> + invocation.getMethodName() + <span class="string">&quot;, provider: &quot;</span> + getUrl() + <span class="string">&quot;, cause: &quot;</span> + e.getMessage(), e);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemotingException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RpcException(RpcException.NETWORK_EXCEPTION, <span class="string">&quot;Failed to invoke remote method: &quot;</span> + invocation.getMethodName() + <span class="string">&quot;, provider: &quot;</span> + getUrl() + <span class="string">&quot;, cause: &quot;</span> + e.getMessage(), e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// AbstractInvoker</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Result <span class="title">invoke</span><span class="params">(Invocation inv)</span> <span class="keyword">throws</span> RpcException </span>&#123;</span><br><span class="line">    <span class="comment">// if invoker is destroyed due to address refresh from registry, let&#x27;s allow the current invoke to proceed</span></span><br><span class="line">    <span class="keyword">if</span> (destroyed.get()) &#123;</span><br><span class="line">        logger.warn(<span class="string">&quot;Invoker for service &quot;</span> + <span class="keyword">this</span> + <span class="string">&quot; on consumer &quot;</span> + NetUtils.getLocalHost() + <span class="string">&quot; is destroyed, &quot;</span></span><br><span class="line">                + <span class="string">&quot;, dubbo version is &quot;</span> + Version.getVersion() + <span class="string">&quot;, this invoker should not be used any longer&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    RpcInvocation invocation = (RpcInvocation) inv;</span><br><span class="line">    invocation.setInvoker(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">if</span> (CollectionUtils.isNotEmptyMap(attachment)) &#123;</span><br><span class="line">        invocation.addAttachmentsIfAbsent(attachment);</span><br><span class="line">    &#125;</span><br><span class="line">    Map&lt;String, String&gt; contextAttachments = RpcContext.getContext().getAttachments();</span><br><span class="line">    <span class="keyword">if</span> (CollectionUtils.isNotEmptyMap(contextAttachments)) &#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">            * invocation.addAttachmentsIfAbsent(context)&#123;<span class="doctag">@link</span> RpcInvocation#addAttachmentsIfAbsent(Map)&#125;should not be used here,</span></span><br><span class="line"><span class="comment">            * because the &#123;<span class="doctag">@link</span> RpcContext#setAttachment(String, String)&#125; is passed in the Filter when the call is triggered</span></span><br><span class="line"><span class="comment">            * by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is</span></span><br><span class="line"><span class="comment">            * a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">        invocation.addAttachments(contextAttachments);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, <span class="keyword">false</span>)) &#123;</span><br><span class="line">        invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());</span><br><span class="line">    &#125;</span><br><span class="line">    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> doInvoke(invocation);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InvocationTargetException e) &#123; <span class="comment">// biz exception</span></span><br><span class="line">        Throwable te = e.getTargetException();</span><br><span class="line">        <span class="keyword">if</span> (te == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> RpcResult(e);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (te <span class="keyword">instanceof</span> RpcException) &#123;</span><br><span class="line">                ((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> RpcResult(te);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RpcException e) &#123;</span><br><span class="line">        <span class="keyword">if</span> (e.isBiz()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> RpcResult(e);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> e;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> RpcResult(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// DubboInvoker</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> Result <span class="title">doInvoke</span><span class="params">(<span class="keyword">final</span> Invocation invocation)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">    RpcInvocation inv = (RpcInvocation) invocation;</span><br><span class="line">    <span class="keyword">final</span> String methodName = RpcUtils.getMethodName(invocation);</span><br><span class="line">    inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());</span><br><span class="line">    inv.setAttachment(Constants.VERSION_KEY, version);</span><br><span class="line"></span><br><span class="line">    ExchangeClient currentClient;</span><br><span class="line">    <span class="keyword">if</span> (clients.length == <span class="number">1</span>) &#123;</span><br><span class="line">        currentClient = clients[<span class="number">0</span>];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        currentClient = clients[index.getAndIncrement() % clients.length];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">boolean</span> isAsync = RpcUtils.isAsync(getUrl(), invocation);</span><br><span class="line">        <span class="keyword">boolean</span> isAsyncFuture = RpcUtils.isReturnTypeFuture(inv);</span><br><span class="line">        <span class="keyword">boolean</span> isOneway = RpcUtils.isOneway(getUrl(), invocation);</span><br><span class="line">        <span class="keyword">int</span> timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);</span><br><span class="line">        <span class="keyword">if</span> (isOneway) &#123;</span><br><span class="line">            <span class="keyword">boolean</span> isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, <span class="keyword">false</span>);</span><br><span class="line">            currentClient.send(inv, isSent);</span><br><span class="line">            RpcContext.getContext().setFuture(<span class="keyword">null</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> RpcResult();</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isAsync) &#123;</span><br><span class="line">            ResponseFuture future = currentClient.request(inv, timeout);</span><br><span class="line">            <span class="comment">// For compatibility</span></span><br><span class="line">            FutureAdapter&lt;Object&gt; futureAdapter = <span class="keyword">new</span> FutureAdapter&lt;&gt;(future);</span><br><span class="line">            RpcContext.getContext().setFuture(futureAdapter);</span><br><span class="line"></span><br><span class="line">            Result result;</span><br><span class="line">            <span class="keyword">if</span> (isAsyncFuture) &#123;</span><br><span class="line">                <span class="comment">// register resultCallback, sometimes we need the async result being processed by the filter chain.</span></span><br><span class="line">                result = <span class="keyword">new</span> AsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), <span class="keyword">false</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                result = <span class="keyword">new</span> SimpleAsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), <span class="keyword">false</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            RpcContext.getContext().setFuture(<span class="keyword">null</span>);</span><br><span class="line">            <span class="keyword">return</span> (Result) currentClient.request(inv, timeout).get();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (TimeoutException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RpcException(RpcException.TIMEOUT_EXCEPTION, <span class="string">&quot;Invoke remote method timeout. method: &quot;</span> + invocation.getMethodName() + <span class="string">&quot;, provider: &quot;</span> + getUrl() + <span class="string">&quot;, cause: &quot;</span> + e.getMessage(), e);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemotingException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RpcException(RpcException.NETWORK_EXCEPTION, <span class="string">&quot;Failed to invoke remote method: &quot;</span> + invocation.getMethodName() + <span class="string">&quot;, provider: &quot;</span> + getUrl() + <span class="string">&quot;, cause: &quot;</span> + e.getMessage(), e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;在这里面会调用到HeaderExchangeClient的request方法，这里底层就是和netty打交道的地方了。从request方法中就会继续调用HeaderExchangeChannel的request方法，</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// HeaderExchangeClient</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ResponseFuture <span class="title">request</span><span class="params">(Object request)</span> <span class="keyword">throws</span> RemotingException </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> channel.request(request);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// HeaderExchangeChannel</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ResponseFuture <span class="title">request</span><span class="params">(Object request)</span> <span class="keyword">throws</span> RemotingException </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ResponseFuture <span class="title">request</span><span class="params">(Object request, <span class="keyword">int</span> timeout)</span> <span class="keyword">throws</span> RemotingException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (closed) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RemotingException(<span class="keyword">this</span>.getLocalAddress(), <span class="keyword">null</span>, <span class="string">&quot;Failed to send request &quot;</span> + request + <span class="string">&quot;, cause: The channel &quot;</span> + <span class="keyword">this</span> + <span class="string">&quot; is closed!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// create request.</span></span><br><span class="line">    Request req = <span class="keyword">new</span> Request();</span><br><span class="line">    req.setVersion(Version.getProtocolVersion());</span><br><span class="line">    req.setTwoWay(<span class="keyword">true</span>);</span><br><span class="line">    req.setData(request);</span><br><span class="line">    DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        channel.send(req);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemotingException e) &#123;</span><br><span class="line">        future.cancel();</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> future;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这里最终会经过AbstractChannel调用NettyChannel发送请求。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NettyChanne</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">send</span><span class="params">(Object message, <span class="keyword">boolean</span> sent)</span> <span class="keyword">throws</span> RemotingException </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>.send(message, sent);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">boolean</span> success = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">int</span> timeout = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        ChannelFuture future = channel.write(message);</span><br><span class="line">        <span class="keyword">if</span> (sent) &#123;</span><br><span class="line">            timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);</span><br><span class="line">            success = future.await(timeout);</span><br><span class="line">        &#125;</span><br><span class="line">        Throwable cause = future.getCause();</span><br><span class="line">        <span class="keyword">if</span> (cause != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> cause;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RemotingException(<span class="keyword">this</span>, <span class="string">&quot;Failed to send message &quot;</span> + message + <span class="string">&quot; to &quot;</span> + getRemoteAddress() + <span class="string">&quot;, cause: &quot;</span> + e.getMessage(), e);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RemotingException(<span class="keyword">this</span>, <span class="string">&quot;Failed to send message &quot;</span> + message + <span class="string">&quot; to &quot;</span> + getRemoteAddress()</span><br><span class="line">                + <span class="string">&quot;in timeout(&quot;</span> + timeout + <span class="string">&quot;ms) limit&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="popular-posts-header">相关文章</div><ul class="popular-posts"><li class="popular-posts-item"><div class="popular-posts-title"><a href="\hexoblog\md\rpc\dubbo\dubboSPI扩展机制基本使用，自动包装\" rel="bookmark">dubboSPI扩展机制基本使用，自动包装</a></div></li><li class="popular-posts-item"><div class="popular-posts-title"><a href="\hexoblog\md\rpc\dubbo\dubboSPI扩展机制自适应\" rel="bookmark">dubboSPI扩展机制自适应</a></div></li><li class="popular-posts-item"><div class="popular-posts-title"><a href="\hexoblog\md\rpc\dubbo\dubboSPI扩展点自动激活\" rel="bookmark">dubboSPI扩展点自动激活</a></div></li><li class="popular-posts-item"><div class="popular-posts-title"><a href="\hexoblog\md\rpc\dubbo\dubbo基础及介绍\" rel="bookmark">dubbo基础及介绍</a></div></li><li class="popular-posts-item"><div class="popular-posts-title"><a href="\hexoblog\md\rpc\dubbo\dubbo服务引入流程\" rel="bookmark">dubbo服务引入流程</a></div></li></ul><footer class="post-footer"><div class="post-copyright"><ul><li class="post-copyright-author"><strong>本文作者： </strong>九分石人</li><li class="post-copyright-link"><strong>本文链接：</strong> <a href="https://linqiankun.github.io/hexoblog/md/rpc/dubbo/dubbo%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E8%BF%87%E7%A8%8B/" title="dubbo服务调用过程">https://linqiankun.github.io/hexoblog/md/rpc/dubbo/dubbo服务调用过程/</a></li><li class="post-copyright-license"><strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/zh-CN" rel="noopener" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！</li></ul></div><div class="followme"><span>欢迎关注我的其它发布渠道</span><div class="social-list"><div class="social-item"><a target="_blank" class="social-link" href="https://github.com/linqiankun"><span class="icon"><i class="fab fa-github"></i> </span><span class="label">GitHub</span></a></div><div class="social-item"><a target="_blank" class="social-link" href="https://gitee.com/linqiankun"><span class="icon"><i class="fab fa-git"></i> </span><span class="label">Gitee</span></a></div><div class="social-item"><a target="_blank" class="social-link" href="/hexoblog/images/thefive.jpg"><span class="icon"><i class="fab fa-weixin"></i> </span><span class="label">WeChat</span></a></div><div class="social-item"><a target="_blank" class="social-link" href="/hexoblog/atom.xml"><span class="icon"><i class="fa fa-rss"></i> </span><span class="label">RSS</span></a></div></div></div><div class="post-tags"><a href="/hexoblog/tags/dubbo/" rel="tag"><i class="fa fa-tag"></i> dubbo</a> <a href="/hexoblog/tags/rpc/" rel="tag"><i class="fa fa-tag"></i> rpc</a></div><div class="post-nav"><div class="post-nav-item"><a href="/hexoblog/md/rpc/dubbo/dubbo%E5%9F%BA%E7%A1%80%E5%8F%8A%E4%BB%8B%E7%BB%8D/" rel="prev" title="dubbo基础及介绍"><i class="fa fa-chevron-left"></i> dubbo基础及介绍</a></div><div class="post-nav-item"><a href="/hexoblog/md/rpc/dubbo/dubboSPI%E6%89%A9%E5%B1%95%E6%9C%BA%E5%88%B6%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8%EF%BC%8C%E8%87%AA%E5%8A%A8%E5%8C%85%E8%A3%85/" rel="next" title="dubboSPI扩展机制基本使用，自动包装">dubboSPI扩展机制基本使用，自动包装 <i class="fa fa-chevron-right"></i></a></div></div></footer></article></div><script>window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      const commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }</script></div></main><footer class="footer"><div class="footer-inner"><div class="copyright">&copy; Tue Mar 02 2021 08:00:00 GMT+0800 (中国标准时间) – <span itemprop="copyrightYear">2025</span> <span class="with-love"><i class="fa fa-heart"></i> </span><span class="author" itemprop="copyrightHolder">九分石人</span></div><div class="wordcount"><span class="post-meta-item"><span class="post-meta-item-icon"><i class="fa fa-chart-line"></i> </span><span title="站点总字数">682k</span> </span><span class="post-meta-item"><span class="post-meta-item-icon"><i class="fa fa-coffee"></i> </span><span title="站点阅读时长">10:20</span></span></div><div class="busuanzi-count"><span class="post-meta-item" id="busuanzi_container_site_uv" style="display:none"><span class="post-meta-item-icon"><i class="fa fa-user"></i> </span><span class="site-uv" title="总访客量"><span id="busuanzi_value_site_uv"></span> </span></span><span class="post-meta-item" id="busuanzi_container_site_pv" style="display:none"><span class="post-meta-item-icon"><i class="fa fa-eye"></i> </span><span class="site-pv" title="总访问量"><span id="busuanzi_value_site_pv"></span></span></span></div></div></footer><script size="300" alpha="0.6" zindex="-1" src="https://unpkg.com/ribbon.js@1.0.2/dist/ribbon.min.js"></script><script src="https://unpkg.com/animejs@3.2.1/lib/anime.min.js"></script><script src="https://unpkg.com/jquery@3.6.0/dist/jquery.min.js"></script><script src="https://unpkg.com/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script><script src="https://unpkg.com/pangu@4.0.7/dist/browser/pangu.min.js"></script><script src="/hexoblog/js/utils.js"></script><script src="/hexoblog/js/motion.js"></script><script src="/hexoblog/js/next-boot.js"></script><script src="/hexoblog/js/bookmark.js"></script><script src="/hexoblog/js/local-search.js"></script><script>var mermaidElements = document.querySelectorAll('.mermaid');
if (mermaidElements.length) {
  NexT.utils.getScript('https://unpkg.com/mermaid@8.9.2/dist/mermaid.min.js', () => {
    mermaidElements.forEach(element => {
      const newElement = document.createElement('div');
      newElement.innerHTML = element.innerHTML;
      newElement.className = element.className;
      element.parentNode.replaceChild(newElement, element);
    });

    mermaid.init({
      theme    : 'default',
      logLevel : 3,
      flowchart: { curve     : 'linear' },
      gantt    : { axisFormat: '%m/%d/%Y' },
      sequence : { actorMargin: 50 }
    }, '.mermaid');
  }, window.mermaid);
}</script><script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script src="https://unpkg.com/darkmode-js@1.5.7/lib/darkmode-js.min.js"></script><script>var options = {
  bottom: '64px',
  right: 'unset',
  left: '32px',
  time: '0.5s',
  mixColor: 'transparent',
  backgroundColor: 'transparent',
  buttonColorDark: '#100f2c',
  buttonColorLight: '#fff',
  saveInCookies: true,
  label: '🌓',
  autoMatchOsTheme: true
}
const darkmode = new Darkmode(options);
window.darkmode = darkmode;
darkmode.showWidget();</script></body></html>